/**
 * Wihidum - Efficient Broker Hierarchy for Publish Subscribe Middleware
 *
 * License      :   Apache License 2.0
 * Home Page    :   http://code.google.com/p/wihidum/
 * Mailing List :   wihidum@googlegroups.com
 */

package lk.mrt.cse.wihidum.communicator;

import lk.mrt.cse.wihidum.processor.PublicationMessage;
import lk.mrt.cse.wihidum.storage.*;
import lk.mrt.cse.wihidum.util.*;
import org.apache.axiom.om.*;
import org.apache.axiom.soap.*;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.*;
import org.apache.axis2.context.MessageContext;
import org.apache.log4j.Logger;

import javax.xml.namespace.QName;
import java.util.*;

public class TreeRouter
        implements Router {

    //Topology topology;
    private static final Logger logger = Logger.getLogger(TreeRouter.class);

    public TreeRouter() {
        //XMLReader xmlReader = new XMLReader("TreeConfig.xml");
        //topology = xmlReader.readXmlDocument();
        if(!Topology.isReadOnce())
        {
            logger.info("Tree Config was not read before.");
            XMLReader xmlReader = new XMLReader();
            //Use this object to get information needed.
            xmlReader.readXmlDocument("TreeConfig.xml");
        }
        else
        {
            logger.info("Tree config is taken from in memory static instance.");
        }
    }

    public void propagateSubscription(SOAPEnvelope message, String filter, String action, String logUUID) {
        String incommingAddress = null;
        String incommingUnit = null;

        String myAddress = Topology.getMyAddress();

        boolean isAlreadySubscribed = true;

        if (action.equals(WihidumConstants.Actions.Subscribe)) {
            incommingUnit = "LocalUnit";
        } else if (action.equals(WihidumConstants.Operations.BROKER_SUBSCRIBE)) {
            try {
                SOAPBody body = message.getBody();
                OMElement addressElement = body.getFirstChildWithName(new QName(WSEventingConstants.WIHIDUM_NAMESPACE, "BrokerSubscription"))
                        .getFirstChildWithName(new QName(WSEventingConstants.WIHIDUM_NAMESPACE, "From"))
                        .getFirstChildWithName(new QName(WSEventingConstants.WIHIDUM_NAMESPACE, "Address"));
                incommingAddress = addressElement.getText();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        for (NeighbourBroker broker : Topology.getSuperUnits()) {
            if (broker.getAddress().equals(incommingAddress)) {
                incommingUnit = "SuperUnit";
                break;
            }
        }
        for (NeighbourBroker broker : Topology.getSubUnits()) {
            if (broker.getAddress().equals(incommingAddress)) {
                incommingUnit = "SubUnit";
                break;
            }
        }
        if (incommingUnit == null) {
            logger.info("Could not locate the incoming location");
        } else if (incommingUnit.equals("LocalUnit")) {
            for (NeighbourBroker broker : getSuperUnits()) {
                if (!BrokerSubscriptionMap.isSubscribedTo(broker, filter)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), broker.getBrokerID(), myAddress, filter, logUUID);
                    forwardSubscription(envelope, broker, myAddress, filter);
                    isAlreadySubscribed = false;
                }
            }
            for (NeighbourBroker broker : getSubUnits()) {
                if (!BrokerSubscriptionMap.isSubscribedTo(broker, filter)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), broker.getBrokerID(), myAddress, filter, logUUID);
                    forwardSubscription(envelope, broker, myAddress, filter);
                    isAlreadySubscribed = false;
                }
            }
        } else if (incommingUnit.equals("SuperUnit")) {
            for (NeighbourBroker broker : getSubUnits()) {
                if (!BrokerSubscriptionMap.isSubscribedTo(broker, filter)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), broker.getBrokerID(), myAddress, filter, logUUID);
                    forwardSubscription(envelope, broker, myAddress, filter);
                    isAlreadySubscribed = false;
                }
            }
        } else if (incommingUnit.equals("SubUnit")) {
            for (NeighbourBroker broker : getSuperUnits()) {
                if (!BrokerSubscriptionMap.isSubscribedTo(broker, filter)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), broker.getBrokerID(), myAddress, filter, logUUID);
                    forwardSubscription(envelope, broker, myAddress, filter);
                    isAlreadySubscribed = false;
                }
            }
            for (NeighbourBroker broker : getSubUnits()) {
                if (!broker.getAddress().equals(incommingAddress) && !BrokerSubscriptionMap.isSubscribedTo(broker, filter)) {
                    SOAPEnvelope envelope = updateBrokerSubscription(broker.getAddress(), broker.getBrokerID(), myAddress, filter, logUUID);
                    forwardSubscription(envelope, broker, myAddress, filter);
                    isAlreadySubscribed = false;
                }
            }
        }

        if (isAlreadySubscribed) {
            logger.info("Tree topology subscription process completed for message with ID#" +logUUID);
        }
    }

    public void propagatePublication(PublicationMessage message) {

    }

    private SOAPEnvelope updateBrokerSubscription(String toAddress, String toId, String fromAddress, String filter, String logUUID) {
        logger.info("Updating the broker subscription");

        SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();

        SOAPEnvelope envelop = factory.getDefaultEnvelope();

        OMNamespace eventingNS = factory.createOMNamespace(WSEventingConstants.EVENTING_NAMESPACE, WSEventingConstants.EVENTING_PREFIX);
        envelop.declareNamespace(eventingNS);
        OMNamespace addressingNS = factory.createOMNamespace(WSEventingConstants.ADDRESSING_NAMESPACE, WSEventingConstants.ADDRESSING_PREFIX);
        envelop.declareNamespace(addressingNS);
        OMNamespace wihidumNS = factory.createOMNamespace(WSEventingConstants.WIHIDUM_NAMESPACE, WSEventingConstants.WHIDUM_NAMESPACE_PREFIX);
        envelop.declareNamespace(wihidumNS);

        OMElement brokerSubscriptionElement = factory.createOMElement("BrokerSubscription", wihidumNS);
        OMElement actionElement = factory.createOMElement("Action", wihidumNS);
        OMElement toElement = factory.createOMElement("To", wihidumNS);
        OMElement toAddressElement = factory.createOMElement("Address", wihidumNS);
        OMElement toIdElement = factory.createOMElement("ID", wihidumNS);
        OMElement fromElement = factory.createOMElement("From", wihidumNS);
        OMElement fromAddressElement = factory.createOMElement("Address", wihidumNS);
        OMElement fromIdElement = factory.createOMElement("ID", wihidumNS);
        OMElement filterElement = factory.createOMElement("Filter", wihidumNS);
        OMElement distributionTraceElement = factory.createOMElement("DistributionState", wihidumNS);
        OMElement startStateElement = factory.createOMElement("StartState", wihidumNS);
        OMElement logIDElement=factory.createOMElement("LogID",wihidumNS);
        
        actionElement.setText(WihidumConstants.Operations.BROKER_SUBSCRIBE);
        toAddressElement.setText(toAddress);
        toIdElement.setText(toId);
        fromAddressElement.setText(fromAddress);
        fromIdElement.setText(Topology.getMyId());
        filterElement.setText(filter);
        startStateElement.setText("Start");
        logIDElement.setText(logUUID);

        toElement.addChild(toAddressElement);
        toElement.addChild(toIdElement);
        fromElement.addChild(fromAddressElement);
        fromElement.addChild(fromIdElement);
        distributionTraceElement.addChild(startStateElement);
        brokerSubscriptionElement.addChild(actionElement);
        brokerSubscriptionElement.addChild(toElement);
        brokerSubscriptionElement.addChild(fromElement);
        brokerSubscriptionElement.addChild(filterElement);
        brokerSubscriptionElement.addChild(logIDElement);

        envelop.getBody().addChild(brokerSubscriptionElement);
        envelop.getBody().addChild(distributionTraceElement);

        return envelop;
    }

    private void forwardSubscription(SOAPEnvelope message, NeighbourBroker broker, String myAddress, String filter) {

        logger.info("Propogating subscription message");

        try {
            ServiceClient serviceClient = new ServiceClient();
            OperationClient opClient = serviceClient.createClient(ServiceClient.ANON_OUT_IN_OP);
            MessageContext outMsgCtx = new MessageContext();
            Options opts = outMsgCtx.getOptions();
            opts.setTo(new EndpointReference(broker.getAddress()));
            opts.setAction(WihidumConstants.Operations.BROKER_SUBSCRIBE);
            outMsgCtx.setEnvelope(message);
            opClient.addMessageContext(outMsgCtx);
            opClient.execute(true);

            BrokerSubscription brokerSubscription = new BrokerSubscription();
            brokerSubscription.setToBrokerAddress(broker.getAddress());
            brokerSubscription.setToBrokerID(broker.getBrokerID());
            brokerSubscription.setTopic(filter);

            String brokerSubscriptionUUID= UUID.randomUUID().toString();
            BrokerSubscriptionMap.saveSubscriber(brokerSubscriptionUUID,brokerSubscription);

        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
   
   private OMElement addBrokerPublicationElement(NeighbourBroker neighbourBroker, OMNamespace wihidumNS, String messageID)
       {
           logger.debug("Adding the broker publication element");
           SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();

           OMElement brokerPublicationElement = factory.createOMElement(WihidumConstants.MessageElements.BROKER_PUBLICATION_HEADER, wihidumNS);
           OMElement actionElement = factory.createOMElement("Action", wihidumNS);
           actionElement.setText(WihidumConstants.Operations.BROKER_PUBLISH);
           brokerPublicationElement.addChild(actionElement);

           OMElement messageIDElement = factory.createOMElement("MessageID", wihidumNS);
           messageIDElement.setText(messageID);
           brokerPublicationElement.addChild(messageIDElement);

           OMElement toElement = factory.createOMElement("To", wihidumNS);
           OMElement toAdressElement = factory.createOMElement("Address", wihidumNS);
           toAdressElement.setText(neighbourBroker.getAddress());
           toElement.addChild(toAdressElement);
           brokerPublicationElement.addChild(toElement);

           OMElement fromElement = factory.createOMElement("From", wihidumNS);
           OMElement fromAddressElement = factory.createOMElement("Address", wihidumNS);
           fromAddressElement.setText(Topology.getMyAddress());
           fromElement.addChild(fromAddressElement);
           brokerPublicationElement.addChild(fromElement);

           OMElement distributionTraceElement = factory.createOMElement("DistributionTrace", wihidumNS);
           OMElement startStationElement = factory.createOMElement("StartStation", wihidumNS);
           startStationElement.setText(Topology.getMyId());
           distributionTraceElement.addChild(startStationElement);
           brokerPublicationElement.addChild(distributionTraceElement);

           return brokerPublicationElement;
       }

       private OMElement updateBrokerPublicationElement(PublicationMessage publicationMessage, NeighbourBroker neighbourBroker, OMNamespace wihidumNS)
       {
           logger.debug("Updating the broker publication element");

           SOAPFactory factory = OMAbstractFactory.getSOAP11Factory();

           OMElement brokerPublicationElement = factory.createOMElement("BrokerPublicationHeader", wihidumNS);
           OMElement actionElement = factory.createOMElement("Action", wihidumNS);
           actionElement.setText(WihidumConstants.Operations.BROKER_PUBLISH);
           brokerPublicationElement.addChild(actionElement);

           OMElement messageIDElement = factory.createOMElement("MessageID", wihidumNS);
           messageIDElement.setText(publicationMessage.getMessageID());
           brokerPublicationElement.addChild(messageIDElement);

           OMElement toElement = factory.createOMElement("To", wihidumNS);
           OMElement toAdressElement = factory.createOMElement("Address", wihidumNS);
           toAdressElement.setText(neighbourBroker.getAddress());
           toElement.addChild(toAdressElement);
           brokerPublicationElement.addChild(toElement);

           OMElement fromElement = factory.createOMElement("From", wihidumNS);
           OMElement fromAddressElement = factory.createOMElement("Address", wihidumNS);
           fromAddressElement.setText(Topology.getMyAddress());
           fromElement.addChild(fromAddressElement);
           brokerPublicationElement.addChild(fromElement);

           OMElement distributionTraceElement = factory.createOMElement("DistributionTrace", wihidumNS);
           OMElement startStationElement = factory.createOMElement("StartStation", wihidumNS);
           startStationElement.setText(publicationMessage.getBrokerPublicationElementStartStation());
           distributionTraceElement.addChild(startStationElement);

           ArrayList<String> intermediateStations = publicationMessage.getBrokerPublicationElementIntermediateStations();

           for (String station : intermediateStations)
           {
               OMElement intermediateStationElement = factory.createOMElement("IntermediateStation", wihidumNS);
               intermediateStationElement.setText(station);
               distributionTraceElement.addChild(intermediateStationElement);
           }
           OMElement intermediateStationElement = factory.createOMElement("IntermediateStation", wihidumNS);
           intermediateStationElement.setText(Topology.getMyId());
           distributionTraceElement.addChild(intermediateStationElement);
           brokerPublicationElement.addChild(distributionTraceElement);

           return brokerPublicationElement;
       }
    
       public SOAPEnvelope createPublicationRequest(PublicationMessage publicationMessage, NeighbourBroker superUnit, String SOAPURI)
       {
           try
           {
               SOAPFactory soapFactory = null;
               if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(SOAPURI))
               {
                   soapFactory = OMAbstractFactory.getSOAP11Factory();
               }
               else
               {
                   if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(SOAPURI))
                   {
                       soapFactory = OMAbstractFactory.getSOAP12Factory();
                   }
                   else
                   {
                       throw new Exception("Unknown SOAP version");
                   }
               }

               SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();

               //declare the used namespaces in the SOAP Envelope
               OMNamespace wseventingNS = soapFactory.createOMNamespace(WSEventingConstants.EVENTING_NAMESPACE, WSEventingConstants.EVENTING_PREFIX);
               soapEnvelope.declareNamespace(wseventingNS);
               OMNamespace wsaNS = soapFactory.createOMNamespace(WSEventingConstants.ADDRESSING_NAMESPACE, WSEventingConstants.ADDRESSING_PREFIX);
               soapEnvelope.declareNamespace(wsaNS);
               OMNamespace wihidumNS = soapFactory.createOMNamespace(WSEventingConstants.WIHIDUM_NAMESPACE, WSEventingConstants.WHIDUM_NAMESPACE_PREFIX);
               soapEnvelope.declareNamespace(wihidumNS);

               OMFactory factory = OMAbstractFactory.getOMFactory();

               SOAPHeader soapHeader = soapEnvelope.getHeader();


               OMElement publicationHeader = factory.createOMElement(WihidumConstants.MessageElements.PUBLICATION_HEADER, wihidumNS);

               OMElement replyTo = factory.createOMElement("ReplyTo", wihidumNS);
               OMElement address = factory.createOMElement("Address", wihidumNS);
               address.setText(publicationMessage.getEventSourceURL());
               replyTo.addChild(address);
               publicationHeader.addChild(replyTo);

               OMElement topicElement = factory.createOMElement("Topic", wihidumNS);
               topicElement.setText(publicationMessage.getTopic());
               publicationHeader.addChild(topicElement);

               soapHeader.addChild(publicationHeader);

               SOAPBody soapBody = soapEnvelope.getBody();
               soapBody.setText(publicationMessage.getBody());

               OMElement brokerPublicationHeader = null;
               if (publicationMessage.getAction().equals(WihidumConstants.Operations.BROKER_PUBLISH))
               {
                   brokerPublicationHeader = this.updateBrokerPublicationElement(publicationMessage, superUnit, wihidumNS);
               }
               else
               {
                   brokerPublicationHeader = this.addBrokerPublicationElement(superUnit, wihidumNS, publicationMessage.getMessageID());
               }

               soapHeader.addChild(brokerPublicationHeader);

               return soapEnvelope;
           }
           catch (Exception ex)
           {
               logger.error(ex);
               return null;
           }

       }

    public ArrayList<NeighbourBroker> getSuperUnits() {
        return Topology.getSuperUnits();
    }

    public ArrayList<NeighbourBroker> getPeerUnits() {
        return Topology.getPeerUnits();
    }

    public ArrayList<NeighbourBroker> getSubUnits() {
        return Topology.getSubUnits();
    }

    public SOAPEnvelope createResponse() {
        return null;
    }
}
